import os.path
import warnings
import logging.config
from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler
from threading import Lock
import logging


from .common_func.print_logger import PrintLogger
from .u_exception import ArgIsNotHandlerError, ArgIsNotFormatterError, ArgIsNotLoggerError
import_error_warned = False
try:
    from concurrent_log_handler import ConcurrentRotatingFileHandler
except ModuleNotFoundError:
    if not import_error_warned:
        PrintLogger.warning(
            "config:rotating_type = 3 is not valid in ULog, because require module:[concurrent_log_handler] is not installed. "
            "If you want use UYaml, please install [concurrent_log_handler] first")
        import_error_warned = True

# no line method: default_log_formatter equ logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s', "%Y-%m-%d %H:%M:%S")
default_log_formatter = logging.Formatter('%(asctime)s - %(filename)s[pid:%(process)d][line:%(lineno)d] - %(levelname)s: %(message)s',
                                          "%Y-%m-%d %H:%M:%S")

# 增加system级别
SYSTEM_LEVEL = 55
logging.addLevelName(SYSTEM_LEVEL, "SYSTEM")
def system(log, msg, *args, **kwargs):
    log._log(SYSTEM_LEVEL, msg, args, **kwargs)

logging.Logger.system = system


_log_config = {
    "log_name" : "log",
    "log_level" : "INFO",
    "backup_count" : 1000,
    "max_bytes" : 20 * 1024 * 1024,
    "rotating_type" : 3,
    "log_dir" : "."
}


class ULog:
    _instance_lock = Lock()
    _logger = dict()
    _default_log_config = _log_config
    _default_log_name = _log_config.get("log_name", "log")

    @classmethod
    def get_logger(cls, logger_name: str = None, log_config=None):
        """
        获取logger，默认带streamHandler和concurrent的fileHandler, 各种参数从配置文件中获取
        如果logger_name为空，则在logger目录下以默认名做log文件名
        否则：在上级日志目录下建立以 logger_name 命名的子文件夹，并在子文件夹下建立以 logger_name 加.log后缀命名的日志文件
        :param logger_name
        :param log_config
        :return:
        """
        PrintLogger.debug(f"[ULog.get_logger]::日志名:{logger_name}，日志配置：{log_config}")
        if log_config is None:
            log_config = cls._default_log_config
            PrintLogger.debug(f"[ULog.get_logger]::未给出日志配置，使用默认配置：{log_config}")
        if not logger_name:
            logger_name = cls._default_log_config.get("log_name", "log")
            PrintLogger.debug(f"[ULog.get_logger]::未给出日志名称，使用默认名称：{logger_name}")

        if cls._logger.get(logger_name, None):
            return cls._logger.get(logger_name)

        PrintLogger.debug(f"[ULog.get_logger]::构建日志句柄：{logger_name}")
        with cls._instance_lock:
            log_dir = log_config.get("log_dir", "")
            if log_dir:
                super_dir = os.path.dirname(log_dir)
                if super_dir and not os.path.exists(super_dir):
                    raise SyntaxError(f"日志目录不合法或上级目录{super_dir}不存在")
                os.makedirs(log_dir, exist_ok=True)

            if logger_name != cls._default_log_config.get("log_name"):
                log_dir = os.path.join(log_dir, logger_name)
                os.makedirs(log_dir, exist_ok=True)

            log_file = os.path.join(log_dir, logger_name + ".log")
            log_level = eval('logging.' + log_config.get("log_level"))
            use_gzip = True if log_config.get("use_gzip", "True").lower() == "true" else False
            _logger = cls.get_basic_logger(logger_name, log_level, log_file=log_file,
                                       rotating_type=log_config.get("rotating_type"), max_bytes=log_config.get("max_bytes"),
                                       backup_count=log_config.get("backup_count"), use_gzip=use_gzip)

            cls._logger[logger_name] = _logger
        return cls._logger.get(logger_name)

    @staticmethod
    def get_basic_logger(logger_name: str, log_level: int = 20, log_formatter=None,
                         log_file: str = "", rotating_type: int = 0, backup_count: int = 100, max_bytes: int = 1024,
                         net_client=None, emit_func=None, close_func=None, use_gzip: bool = True):
        """
        调用本函数获取logger，设置日志级别，使用格式化器格式化日志输出，默认启动streamHandler.
        fileHandler和网络Handler按需建立
        :param logger_name:
        :param log_level:
        :param log_formatter:
        :param log_file: 日志文件绝对路径， 为空表示不需要文件日志，
        :param rotating_type: 0 表示不回滚，1表示按大小回滚，2表示按天回滚, 3使用concurrent支持多线程
        :param backup_count: 日志文件备份个数
        :param max_bytes: 单个日志文件最大尺寸，以字节为单位
        :param net_client: 网络客户端对象， 值为None表示不需要网络Handler
        :param emit_func: 网络日志发送函数
        :param close_func: 网络客户端关闭或断连函数
        :param use_gzip: 是否对日志进行gzip压缩
        :return:
        """
        PrintLogger.debug(
            f"[ULog.get_basic_logger]::开始构建日志句柄，日志配置：logger_name：{logger_name}, log_level: {log_level}, "
            f"log_formatter={log_formatter}, log_file = {log_file}, rotating_type = {rotating_type}, "
            f"backup_count = {backup_count}, max_bytes = {max_bytes}, net_client={net_client}, emit_func={emit_func}, "
            f"close_func={close_func}, use_gzip: bool = {use_gzip}")
        custom_logger = logging.getLogger(logger_name)
        custom_logger.setLevel(log_level)

        if not isinstance(log_formatter, logging.Formatter):
            log_formatter = default_log_formatter

        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(log_formatter)
        stream_handler.setLevel(log_level)
        custom_logger.addHandler(stream_handler)

        if log_file:
            if rotating_type == 1:
                file_handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
            elif rotating_type == 2:
                file_handler = TimedRotatingFileHandler(log_file, when="D", interval=1, backupCount=backup_count)
            elif rotating_type == 3:
                try:
                    file_handler = ConcurrentRotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count,
                                                                 encoding='utf-8', use_gzip=use_gzip)
                except NameError:
                    PrintLogger.warning(
                        "config:rotating_type = 3 is not valid in ULog, because require module:[concurrent_log_handler] "
                        "is not installed. If you want use this type, please install [concurrent_log_handler] first. "
                        "Now use RotatingFileHandler instead")
                    file_handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
            else:
                warnings.warn("Suggest using RotatingFileHandler!!")
                file_handler = logging.FileHandler(log_file)
            file_handler.setFormatter(log_formatter)
            file_handler.setLevel(log_level)
            custom_logger.addHandler(file_handler)

        if net_client is not None:
            net_handler = NetHandler(net_client=net_client, emit_func=emit_func, close_func=close_func)
            net_handler.setFormatter(log_formatter)
            net_handler.setLevel(log_level)
            custom_logger.addHandler(net_handler)

        return custom_logger

    @staticmethod
    def get_net_handler(net_client=None, emit_func=None, close_func=None,
                        formatter: logging.Formatter = default_log_formatter):
        """

        :param net_client:
        :param emit_func:
        :param close_func
        :param formatter
        :return:
        """
        net_handler = NetHandler(net_client=net_client, emit_func=emit_func, close_func=close_func)
        net_handler.setFormatter(formatter)
        return net_handler

    @staticmethod
    def get_rotating_file_handler(log_file: str, max_bytes: int, backup_count: int,
                                  formatter: logging.Formatter = default_log_formatter):
        """
        Split logs and divide files by max bytes.
        max bytes set to 20MB, backup count set to 100, e.g. max total log size will limit to 2GB.

        Args:
            log_file: log file path
            max_bytes:
            backup_count:
            formatter:

        Returns:  rotating_file_handler

        """
        rotating_file_handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
        rotating_file_handler.setFormatter(formatter)
        return rotating_file_handler

    @staticmethod
    def get_time_rotating_file_handler(log_file: str, time_unit: str = "D", interval: int = 1, backup_count: int = 100,
                                       formatter: logging.Formatter = default_log_formatter):
        """
        Split logs and divide files by arguments, return a log handler
        :param log_file:
        :param time_unit:
        :param interval:
        :param backup_count:
        :param formatter:
        :return:
        """
        rotating_file_handler = TimedRotatingFileHandler(log_file, when=time_unit, interval=interval,
                                                         backupCount=backup_count)
        rotating_file_handler.setFormatter(formatter)
        return rotating_file_handler

    @staticmethod
    def get_concurrent_file_handler(log_file: str, max_bytes: int, backup_count: int,
                                    formatter: logging.Formatter = default_log_formatter):
        """
        Handler for logging to a set of files, which switches from one file to the
        next when the current file reaches a certain size. Multiple processes can
        write to the log file concurrently, but this may mean that the file will
        exceed the given size.

        Args:
            log_file: log file path
            max_bytes:
            backup_count:
            formatter:

        Returns:  _handler

        """
        _handler = ConcurrentRotatingFileHandler(log_file, maxBytes=max_bytes,
                                                 backupCount=backup_count, encoding='utf-8')
        _handler.setFormatter(formatter)
        return _handler

    @staticmethod
    def get_file_handler(log_file: str, formatter: logging.Formatter = default_log_formatter):
        """
        get file_handler configuration
        Args:
            log_file: log file path
            formatter:

        Returns:

        """
        warnings.warn("Suggest using get_rotating_file_handler!!")
        file_handler = logging.FileHandler(log_file)
        file_handler.setFormatter(formatter)
        return file_handler

    @staticmethod
    def get_stream_handler(formatter: logging.Formatter = default_log_formatter):
        """
        Get stream_handler configuration
        :param formatter
        :return stream_handler:
        """
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        return stream_handler

    @classmethod
    def set_formatter(cls, formatter: logging.Formatter, handler: logging.Handler = None, logger: logging.Logger = None):
        """
        给日志handler添加格式化器，不指定格式化器，则用默认进行配置
        :param handler:
        :param formatter:
        :param logger:
        :return:
        """
        if handler is None and logger is None:
            logger = cls._logger.get(cls._default_log_name)

        if not isinstance(formatter, logging.Formatter):
            raise ArgIsNotFormatterError

        if handler:
            if not isinstance(handler, logging.Handler):
                raise ArgIsNotHandlerError
            else:
                handler.setFormatter(formatter)
        if logger:
            if not isinstance(logger, logging.Logger):
                raise ArgIsNotLoggerError
            else:
                for _handler in logger.handlers:
                    _handler.setFormatter(formatter)


class NetHandler(logging.Handler):
    def __init__(self, net_client, emit_func, close_func):
        """
        网络日志Handler，需要注册网络日志发送程序
        :param net_client: 网络客户端对象
        :param emit_func: 网络日志发送函数, 函数定义格式 def func_name(client, msg)
        :param close_func: 网络客户端关闭或断连函数
        """
        logging.Handler.__init__(self)
        self.client = net_client
        self.count = 0
        self.emit_func = emit_func
        self.close_func = close_func

    def emit(self, record):
        msg = self.format(record)
        if self.client.connected:
            self.emit_func(self.client, msg)
            self.count += 1

    def __del__(self):
        self.close_func(self.client)
